package org.naxx.security.xgen;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.naxx.security.expression.Node;
import org.naxx.security.expression.Node.NodeType;
import org.naxx.security.model.match.AttributeDesignator;
import org.naxx.security.model.match.MatchParentType;
import org.naxx.security.model.match.ValueHolder;
import org.naxx.security.xgen.util.JavaNameGenerator;
import org.naxx.security.xgen.util.XWriter;

/**
 * Generates Java code from expression tree.<br>
 * Not thread safe.<p>
 * 
 * FIXME AttributeDesignator needs to be fixed ASAP
 */
public final class XExpressionGenerator {
	
	private static final Logger logger = Logger.getLogger(XExpressionGenerator.class);
	
	/**
	 * Key is id of node and values are variable names of its children, 
	 * sorted from left most to right most child.
	 */
	private Map<String, List<String>> variableNames = new HashMap<String, List<String>>();
	private JavaNameGenerator nameGenerator = new JavaNameGenerator(); //TODO use global one (ok 
	private String rootVariableName;					               //for now since only vars are generated and not class names)

	public String generate(XWriter writer, Node root) throws Exception {
		this.traverseExpressionTree(writer, root);
		return this.rootVariableName;
	}
	
	private void traverseExpressionTree(XWriter writer, Node node) throws Exception {
		if(node.getNodeType().equals(NodeType.LEAF)) {
			this.doActualWrite(writer, node);
		}
		else {
			for(Node child : node.getChildren()) {
				this.traverseExpressionTree(writer, child);
			}
			this.doActualWrite(writer, node);
		}
	}
	
	private void doActualWrite(XWriter writer, Node node) throws Exception {
		final String varName = this.nameGenerator.getNextUniqueVariableName();
		if(node.getNodeType().equals(NodeType.LEAF)) {
			if(node.getValueHolder() != null) {
				ValueHolder valueHolder = node.getValueHolder();
				writer.write(valueHolder.getDataType().getJavaTypeName() + " "
						+ varName + " = " + valueHolder.getValue() + ";\n");
			}
			else if(node.getAttributeDesignator() != null) {
				logger.warn("AttributeDesignator found but not implemented yet");
			}
			this.addVariableName(varName, node.getParent());
		}
		else { // gotta use variable names created from children
			String xriptlet = node.getFunctionType().getXriptlet();
			logger.info("Xriptlet loaded '" + xriptlet + "'");
			xriptlet = xriptlet.replace("${resultVarName}", varName);
			final int numberOfParameters = node.getFunctionType().getNumberOfParameters();
			for (int i = 0; i < numberOfParameters; i++) {
				xriptlet = xriptlet.replace("${arg[" + i + "]}", 
						this.variableNames.get(node.getId()).get(i));
			}
			logger.info("Scriptlet after transformation '" + xriptlet + "'");
			writer.write(xriptlet + "\n");
			this.addVariableName(varName, node.getParent());
		}
	}
	
	private void addVariableName(String varName, Node parent) {
		if(parent == null) { // root node
			this.rootVariableName = varName;
			return;
		}
		final String parentId = parent.getId();
		if(!this.variableNames.containsKey(parentId)) {
			this.variableNames.put(parentId, new ArrayList<String>());
		}
		List<String> variableNames = this.variableNames.get(parentId);
		variableNames.add(varName);
	}
	
	public void writeDesignator(XWriter writer, AttributeDesignator designator, 
			String variableName) throws Exception {		
		String javaTypeName = designator.getDataType().getJavaTypeName();
		writer.write("Request request = null;");
		writer.write("List values = request." + this.resolveRequestMethodName(designator.getMatchParentType()) 
				+ "(\"" + designator.getAttributeId() + "\");");
		writer.write("List<" + javaTypeName + ">" + variableName + "= (List<" + javaTypeName + ">) values;");
		writer.write("if(" + variableName + " == null) { " 
				+ variableName + " = Collections.emptyList(); }");
	}
	
	private String resolveRequestMethodName(MatchParentType matchParentType) {
		if(matchParentType.equals(MatchParentType.ACTION)) {
			return "getActionAttributeValues";
		}
		else if(matchParentType.equals(MatchParentType.ENVIRONMENT)) {
			return "getEnvironmentAttributeValues";
		}
		else if(matchParentType.equals(MatchParentType.RESOURCE)) {
			return "getResourceAttributeValues";
		}
		else if(matchParentType.equals(MatchParentType.SUBJECT)) {
			return "getSubjectAttributeValues";
		}
		else {
			throw new IllegalStateException("Unknown MatchParentType enum constant '" + matchParentType + "'");
		}	
	}
}
